﻿using Cofe.Core.Interfaces;
using Cofe.Core.Property;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Cofe.Core.Security
{
    [Export(typeof(ICofeService))]
    [ServicePriority(ServicePriorityAttribute.DefaultPriority_COFE)]
    public class SecurityManager : SecurityManagerBase, ISaveSettings
    {
        public SecurityManager()
            : base()
        {
            ServicePermissionPolicies.Add(VolumePermissionPolicy.ForRootVolume());
            ServicePermissionPolicies.Add(VolumePermissionPolicy.ForUnmappedEntryList());
        }

        public Task SaveAsync(Session.ICofeSettings settings)
        {
            return Task.Run(() =>
            {
                if (CofeServices.SecurityManager == this)
                {
                    settings.Permissions = new Session.PermissionList(PermissionPolicies);

                }
            });
        }

        public Task RestoreAsync(Session.ICofeSettings settings)
        {
            return Task.Run(() =>
            {
                if (CofeServices.SecurityManager == this)
                {
                    List<IPermissionPolicy> permissionPolicies = new List<IPermissionPolicy>();
                    permissionPolicies.AddRange(settings.Permissions);
                    PermissionPolicies = permissionPolicies;
                }
            });
        }
    }

    public abstract class SecurityManagerBase : ISecurityManager
    {
        #region Constructor


        public SecurityManagerBase()
        {

        }

        [Obsolete]
        public SecurityManagerBase(IUserManager userManager)
        {
            _userManager = userManager;
        }

        #endregion

        #region Methods


        public void DemandPermission<T>(T requestedObj, PermissionType permissionType) where T : IProtectedData<T>
        {
            if (permissionType != PermissionType.None)
                if (!(IsPermissionGranted(requestedObj, permissionType)))
                    throw new System.Security.SecurityException(
                        string.Format("No suitable permission policy for {0} {1}", 
                        permissionType, requestedObj.ToString()));
        }

        public bool IsPermissionGranted<T>(T requestedObj, PermissionType permissionType) where T : IProtectedData<T>
        {
            if (permissionType == PermissionType.None)
                return true;

            var policies = GetPermissionPolicies<T>();
            foreach (var policy in policies)
                //If the policy match the current request.
                if (policy.Match((T)requestedObj, permissionType))
                {
                    //If no role specified return immediately.
                    if (policy.RequiredRole == null || policy.RequiredRole.Length == 0)
                        return true;

                    //If any role matched return without exception.
                    foreach (var role in policy.RequiredRole)
                        if (CurrentPrincipal.IsInRole(role))
                            return true;

                }

            return false;
        }

        public IEnumerable<IPermissionPolicy<T>> GetPermissionPolicies<T>()
            where T : IProtectedData<T>
        {
            foreach (var pm in GetPermissionPolicies(p => p is IPermissionPolicy<T>))
                yield return (IPermissionPolicy<T>)pm;
        }

        public IEnumerable<IPermissionPolicy> GetPermissionPolicies(Func<IPermissionPolicy, bool> filter)
        {
            foreach (var pm in PermissionPolicies)
                if (filter(pm))
                    yield return pm;

            foreach (var pm in ServicePermissionPolicies)
                if (filter(pm))
                    yield return pm;

        }

        public void setIdentity(IIdentity identity = null)
        {
            CurrentPrincipal = UserManager.GetPrincipal(identity);
        }

        public CofeImpersonationContext ImpersonateAs(IIdentity identity)
        {
            return new CofeImpersonationContext(identity);
        }

        /// <summary>
        /// Impersonate as a service
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public CofeImpersonationContext ImpersonateAsService(string serviceType)
        {
            return ImpersonateAs(new ServiceIdentity(serviceType));
        }

        public void AddPermissionPolicy(IPermissionPolicy policy)
        {
            _policies.Add(policy);
        }

        public void RemovePermissionPolicy(Func<IPermissionPolicy, bool> filter)
        {
            foreach (var p in _policies.ToArray())
                if (filter(p))
                    _policies.Remove(p);
        }

        #endregion

        #region Data

        private IUserManager _userManager;
        private List<IPermissionPolicy> _servicePolicies = new List<IPermissionPolicy>();
        private List<IPermissionPolicy> _policies = new List<IPermissionPolicy>();

        #endregion

        #region Public Properties


        public IUserManager UserManager { get { return _userManager ?? CofeServices.UserManager; } }
        public IEnumerable<IPermissionPolicy> PermissionPolicies
        {
            get { return _policies; }
            protected set { _policies = value.ToList(); }
        }
        public List<IPermissionPolicy> ServicePermissionPolicies
        {
            get { return _servicePolicies; }
            protected set { _servicePolicies = value.ToList(); }
        }
        public IPrincipal CurrentPrincipal
        {
#if NETFX_CORE
            get;
            set;
#else
            get { return UserManager.GetPrincipal(Thread.CurrentPrincipal.Identity); }
            set { Thread.CurrentPrincipal = value; } 
#endif
        }


        #endregion






    }
}
